'use strict'
import { lib, game, ui, get, ai, _status } from '../../../noname.js'
//import { HDWJ, HDWJFN } from './character/HDWJ.js'
//import { jilueSkSr, jilueSkSrFN } from './character/jilueSkSr.js'
import { swFunc } from './swFunc.js'
import { DIY } from '../character/DIY.js'
export const swTool = {
	extensionName: '云中守望',
	extensionVersion: '1.0',
	myURL() {
		return 'extension_' + swTool.extensionName + '_';
	},
	observingId: [],
	newConnectMode: [],
	modeConfig: {},
	globalSkills: {},
	strings: {},//发给客机执行的函数的json序列化缓存
	//此处只写修改过的本体游戏有的模式
	mode: [
		'identity', 'versus', 'doudizhu'
	],
	jumpToURL(url) {
		setTimeout(
			function() {
				window.open(url);
			},
			500);
	},
	isEmotion(str) {
		const regExp = /^<img\b(?=[^>]*\bsrc="##assetURL##image\/emotion\/([^"\/]+)\/([^"\/]+)\.gif")(?=[^>]*\bwidth="[^"]*")(?=[^>]*\bheight="[^"]*")(?!.*\b(?!src|width|height)\w+=)[^>]*\/?>$/i;
		return regExp.test(str);
	},
	//提前加载脚本
	addImport(url, end = () => {
	}, error = e => console.error(e)) {
		const script = document.createElement('script');
		script.type = 'module';
		script.src = lib.assetURL + url;

		script.onload = () => {
			end();
			script.remove();
		};
		script.onerror = (e) => {
			error(e);
			script.remove();
		};
		document.head.appendChild(script);
		return script;
	},
	//增加联机新模式
	addConnectMode(name, id, pack = {}, js) {
		const extensionName = swTool.extensionName
		if (!lib[id + 'URL']) lib[id + 'URL'] = `ext:${extensionName}/mode/${id}/`
		game.addMode(id, pack, {
			translate: name,
			extension: extensionName,
		});
		lib.mode[id].connect = {};
		swTool.newConnectMode.push(id)
		if (Array.isArray(js)) {
			//游戏开始之前对模式进行初始化配置，此处存储需要配置的文件
			lib.mode[id].mainConfig = js
			//for (let j of js) {
			//	lib.init.js(`${lib.assetURL}extension/${extensionName}/mode/${id}`, j);
			//}
		}
	},
	//该函数会广播发给其他玩家
	loadModeAsync(name, callback, onerror = e => console.error(e)) {
		let promise = (async () => {
			window.game = game;
			const cached = swTool[`${name}Pack`];
			let exports
			if (swTool.newConnectMode.includes(name)) {//是自定义的模式
				exports = cached
					? { default: () => cached }
					: await import(`../../../extension/${swTool.extensionName}/mode/${name}/${name}.js`);
			} else {
				try {
					exports = await import(`../../../mode/${name}.js`);
				} catch {
					exports = {};
				}
			}
			// esm模式，
			if (Object.keys(exports).length > 0) {
				if (typeof exports.default !== "function") {
					throw new Error(`导入的模式[${name}]格式不正确！`);
				}
				game.import("mode", exports.default);
			}
			// 普通模式
			else {
				await new Promise((resolve, reject) => {
					let script = lib.init.js(
						`${lib.assetURL}mode`,
						name,
						() => {
							script?.remove();
							resolve(null);
						},
						e => reject(e.error)
					);
				});
			}
			await Promise.allSettled(_status.importing.mode);
			if (!lib.config.dev) delete window.game;
			const content = lib.imported.mode[name];
			if (!content) throw new Error(`导入的模式[${name}]格式不正确！`);
			delete lib.imported.mode[name];
			if (get.is.empty(lib.imported.mode)) {
				delete lib.imported.mode;
			}
			return content;
		})();
		if (callback) promise = promise.then(callback, onerror);
		return promise;
	},
	playSkillAnimation(player, skillName, vice) {
		//获取武将所有技能
		function getSkills(name) {
			var skills = get.character(name, 3);
			var viewSkill = [];
			for (var skill of skills) {
				var info = get.info(skill);
				console.log("info", info);
				if (!info || info.nopop || !get.translation(skill + "_info")) {
					continue;
				}
				viewSkill.add(skill);
				if (info.derivation) {
					if (typeof info.derivation === "string") {
						viewSkill.add(info.derivation);
					} else {
						for (var s of info.derivation) {
							viewSkill.add(s);
						}
					}
				}
			}
			return viewSkill;
		}

		//获取技能类型
		function getSkillType(skillName) {
			if (!skillName) {
				// get.translation(skill + '_info')
				return "xiandingji";
			}
			var skillType = "";
			if (get.info(skillName).juexingji) {
				skillType = "juexingji";
			} else if (get.info(skillName).limited) {
				skillType = "xiandingji";
			} else if (get.info(skillName).dutySkill) {
				skillType = "shimingji";
			}
			return skillType;
		}

		//获取技能对应的id 决进 => mbjunjin
		function getName(player, oskill) {
			let target = player.skills.findIndex(skill => get.translation(skill) == oskill);
			if (target == -1) {
				//找不到再去找一次
				var viewSkill = getSkills(player.name);
				//双将模式
				if (player.name2) {
					viewSkill = viewSkill.concat(getSkills(player.name2));
				}
				target = viewSkill.findIndex(skill => get.translation(skill) == oskill);
				return target == -1 ? false : viewSkill[target];
			}
			let skill = player.skills[target];
			return skill;
		}

		var name = getName(player, skillName);
		if (get.itemtype(player) != "player") return console.error("player");
		var skillType = getSkillType(name);
		if ("juexingji" == skillType) {
			var avatar_wenzhi = ui.create.div(".juexingjinengming .wmmh-skill-common-jinengming", ui.window);
		} else if ("xiandingji" == skillType) {
			var avatar_wenzhi = ui.create.div(".xiandingwujiangtu .wmmh-skill-common-jinengming", ui.window);
		} else if ("shimingji" == skillType) {
			var avatar_wenzhi = ui.create.div(".shimingwujiangtu .wmmh-skill-common-jinengming", ui.window);
		} else {
			var avatar_wenzhi = ui.create.div(".xiandingwujiangtu .wmmh-skill-common-jinengming", ui.window);
		}
		var avatar = ui.create.div(".juexingwujiangtu", ui.window);
		const rect = player?.node?.avatar?.getBoundingClientRect();
		avatar.style.top = rect.top + 'px';
		avatar.style.left = rect.left + 'px';

		if (!ui.imgContainer) {
			ui.imgContainer = ui.create.div(".child-img", ui.window);
		}
		let currentFrame = 0;
		const imgList = _status.skillAnimationIMG ||
			Array.from({ length: 8 }, (_, i) => ({
				src: `${lib.assetURL}extension/${swTool.extensionName}/assets/animation/skill/${i}.png`
			}));
		const img = document.createElement('img');
		img.src = imgList[currentFrame].src;
		ui.imgContainer.appendChild(img);

		if (vice == "vice") {
			avatar.style.backgroundImage = player.node.avatar2.style.backgroundImage; // 主将立绘
		} else {
			avatar.style.backgroundImage = player.node.avatar.style.backgroundImage; // 副将立绘
		}
		avatar_wenzhi.innerHTML = skillName;

		const intervalId = setInterval(() => {
			currentFrame = (currentFrame + 1) % 8
			img.src = imgList[currentFrame].src;
		}, 300);

		setTimeout(() => {
			clearInterval(intervalId);
			avatar_wenzhi.remove();
			avatar.remove();
			img.remove();
		}, 3100);
	},
	skillAnimationCSS:
		`
	.wmmh-skill-common-jinengming {
	display: block;
	position: fixed;
	left: 50%;
	top: 50%;
	/*color: rgb(190, 56, 134);*/
	font-size: 82px;
	font-family: xingkai;
	font-weight: 300;
	text-shadow: none;
	-webkit-background-clip: text;
	-webkit-text-fill-color: transparent;
	filter: drop-shadow(1px 1px 1px black) drop-shadow(1px 1px 2px black) drop-shadow(1px 1px 3px black) drop-shadow(1px 1px 4px black) drop-shadow(1px 1px 5px black);
	transform: translate(calc(-50% + 40px), calc(-50% + 24px));
	white-space: break-spaces;
	visibility: hidden;
	z-index: 888888 !important;
	animation: avatarming 2.5s linear;
	animation-delay: 0.6s;
	-webkit-text-stroke: 0.5px rgba(0, 0, 0, 0.7);
	animation-fill-mode: forwards;
}

/* 觉醒技 */
/*限定武将图*/
.juexingwujiangtu {
	background-size: cover;
	background-position-x: 50%;
	width: 150px;
	height: 256px;
	/* 275px */
	/* rotate: -10.2deg; */
	border-radius: 8px;
	z-index: 999999 !important;
	visibility: hidden;
	animation: avatartu 3.2s linear;
	animation-fill-mode: forwards;
	box-shadow: 0px 0px 10px 10px rgba(0, 0, 0, 0.4);
}
@keyframes avatartu {
	0% {
		visibility: visible;
		transform: scale(1) rotate(0deg);
	}
	6% {
		top: calc(50% - 125px);
		left: calc(50% - 75px);
		transform: scale(1.5) rotate(0deg);
	}
	20% {
		top: calc(50% - 125px);
		left: calc(50% - 75px);
		transform: scale(2.2) rotate(0deg);
	}
	25% {
		top: calc(50% - 145px);
		left: calc(50% - 229px);
		transform: scale(1) rotate(-10.2deg);
	}
	29% {
		top: calc(50% - 145px);
		left: calc(50% - 229px);
		transform: scale(1.1) rotate(-10.2deg);
	}
	33% {
		top: calc(50% - 145px);
		left: calc(50% - 229px);
		transform: scale(1) rotate(-10.2deg);
	}
	100% {
		top: calc(50% - 145px);
		left: calc(50% - 229px);
		transform: scale(1) rotate(-10.2deg);
	}
}
.wmmh-skill-common-jinengming2 {
	animation: avatarming 2.5s linear;
	animation-delay: 0.6s;
	transform: translate(calc(-50% + 40px), calc(-50% + 44px));
}

@keyframes avatarming {
	0% {
		visibility: visible;
		translate: scale(0);
		opacity: 0;
	}
	18% {
		translate: scale(0);
		opacity: 0;
	}
	34% {
		translate: scale(1);
		opacity: 1;
	}
	100% {
		translate: scale(1);
		opacity: 1;
	}
}
.child-img {
	z-index: 777777 !important;
	/*position: absolute;*/
	width: 400px;
	height: 300px;
	scale: 1.8;
	top: calc(50% - 145px);
	left: calc(50% - 229px);
	pointer-events: none;
}
.child-img img{
	right: 9.8%;
	bottom: 5%;
	position: absolute;
	visibility: hidden;
	animation: avatarBG 3.2s linear;
	animation-fill-mode: forwards;
}
@keyframes avatarBG {
	0% {
		visibility: visible;
		opacity: 0;
	}
	32% {
		opacity: 0;
	}
	50% {
		opacity: 1;
	}
	100% {
		opacity: 1;
	}
}
/*觉醒技能名*/
.juexingjinengming {
	background-image: linear-gradient(180deg, #f1e7f9 30%, #7251ac 68%, #73add1 85%);
}
/* 限定技 */
.xiandingwujiangtu {
	background-image: linear-gradient(180deg, #fcfc70 30%, #ad6e22 68%, #f5f1ce 91%);
}

/* 使命技 */
.shimingwujiangtu {
	background-image: linear-gradient(180deg, #ffff9a 19%, #d9532e 73%, #f5f1ce 96%);
}
 
	`,
	//融合对象
	comboObject(reObject, opts) {
		if (typeof opts !== 'object') return;
		const addObjectKey = (retarget, myTarget) => {
			for (const i in myTarget) {
				if (retarget[i] && typeof myTarget[i] === 'object') {
					addObjectKey(retarget[i], myTarget[i])
				} else {
					retarget[i] = myTarget[i]
				}
			}
		}
		addObjectKey(reObject, opts)
	},
	/**
	 * 获取指定目录下所有文件的完整路径（递归包含子文件夹）
	 * @param {string} path - 要遍历的起始路径
	 * @returns {Promise<string[]>} - 所有文件路径数组
	 */
	getAllFilePaths(path, callback) {
		const result = [];

		function walk(currentPath, done) {
			game.getFileList(currentPath, (folders, files) => {
				files.forEach(file => {
					result.push(`${currentPath}/${file}`);
				});

				let pending = folders.length;
				if (!pending) return done();

				folders.forEach(folder => {
					walk(`${currentPath}/${folder}`, () => {
						pending--;
						if (!pending) done();
					});
				});
			});
		}

		walk(path, () => {
			callback(result);
		});
	},
/**
	 * 创建一个指定标签的 DOM 元素，设置其属性、绑定事件，并可自动插入到指定父节点中
	 * @param {string} tag - 要创建的 HTML 标签名称，例如 'div'、'button'
	 * @param {Object} selection - 要设置的属性对象，例如 innerText、className、style 等
	 * @param {HTMLElement} [parentNode] - 可选参数，指定新创建元素的父节点
	 * @param {string} [event] - 可选参数，要绑定的事件名称，如 'click'、'change' 等
	 * @param {Function|string} [func] - 可选参数，事件触发时执行的函数
	 *
	 * @returns {HTMLElement} - 返回创建并处理好的 DOM 元素对象。
	 */
	node(tag, selection, parentNode, event, func) {
		const node = document.createElement(tag)
		for (const n in selection) {
			node[n] = selection[n]
		}
		if (parentNode) {
			parentNode.appendChild(node)
		}
		if (event) {
			if (typeof func === 'string') {
				func = swTool.selectFun[func]
			} else {
				node.addEventListener(event, func)
			}
		}
		return node
	},
	getSelect() {
		const mode = get.mode()
		let num = lib.configOL[mode + '_listNum'] || lib.configOL[`${mode}_listNum`] || 5
		if (num === 'normal') return 5
		num = !parseInt(num) ? 5 : parseInt(num)
		return num
	},
	connect_players() {
		const players = game.connectPlayers || game.players
		const playertrue = []
		const playersfalse = []
		players.forEach(p => {
			if (p.avatar || p.nickname) playertrue.push(p)
			else playersfalse.push(p)
		})
		return [playertrue, playersfalse]
	},
	//初始化将池
	initList(func = () => {
	}, unforbidai) {
		let letItBand = c => {
			if (!unforbidai && lib.config.forbidai.includes(c)) return true
			if (lib.config.banned.includes(c)) return true
			// if (lib.characterFilter[c] && !lib.characterFilter[c](get.mode())) return true;
			if (lib.configOL.banned.includes(c) || lib.connectBanned.includes(c)) return true;
		}
		lib.connectBanned.remove('shen_diaochan')
		const libCharacter = {}
		lib.configOL.characterPack.forEach(p => {
			const pack = lib.characterPack[p]
			for (const c in pack) {
				func(c, p)
				if (!lib.configOL.banned.includes(c) && !letItBand(c)) {
					if (lib.character[c]) libCharacter[c] = pack[c];
				}
			}
		})
		return libCharacter
	},

	replaceHandcardsnum: 1,
	replaceHandcards(...args) {
		if (swTool.replaceHandcardsnum > lib.config[swTool.myURL() + 'func_replaceHandCards']) return;
		//原版代码部分
		const next = game.createEvent('replaceHandcards');
		if (Array.isArray(args[0])) next.players = args[0];
		else next.players = args.filter(a => get.itemtype(a) == 'player')
		next.setContent(_status.connectMode ? 'replaceHandcardsOL' : 'replaceHandcards')

		swTool.replaceHandcardsOver = true
	},
	//以重连和观战玩家为目标执行配置中对应的函数部分
	executeConnect({ player, version, config, banned_info }) {
		//TODO: 完全可以塞到postreconnect中，还省一个函数
		const syncFiles = [];
		const playerFunction = {
			observeChat(player, version, config, banned_info) {
				//如果还在房间界面就return
				if (_status.waitingForPlayer) return
				var observe = true
				if (config.id && lib.playerOL && lib.playerOL[config.id]) observe = false
				if (lib.config[swTool.myURL() + 'game_observeChat']) {
					if (observe) player.send(ui.create.danmu, '', '提示', '本房允许旁观发言')
					else {
						player.send(ui.create.danmu, '', '提示', '欢迎饱饱重返游戏！')
						let pp = lib.playerOL[config.id]
						if (!pp.nickname || config.nickname != pp.nickname) {
							game.broadcastAll((player, name) => {
								player.nickname = name
								player.setNickname()
							}, pp, config.nickname)
						}
					}
				}
				if (lib.config[swTool.myURL() + 'game_observeEnter']) {
					if (observe) player.send(ui.create.danmu, '', '提示', '本房允许旁观中途加入游戏')
				}
				player.send((observe) => {
					game.send = function() {
						if (game.observe && !['reinited', 'chat', 'emotion'].includes(arguments[0])) return;
						if (game.ws) {
							const args = Array.from(arguments);
							if (['chat', 'emotion'].includes(arguments[0])) args.push(lib.config.connect_nickname)
							if (typeof args[0] == 'function') {
								args.unshift('exec');
							}
							game.ws.send(JSON.stringify(get.stringifiedResult(args)));
						}
					}
					if (!ui.chatButton && observe) ui.create.chat()
				}, observe)
			},
			closeWindowsError(player) {
				player.send(() => {
					window.onerror = function(msg, src, line, column, err) {
					}
				})
			},
			observeEnter(player, version, config, banned_info) {
				if (swTool.isObserving(config.id)) {
					player.send(func => func(), swFunc.observeEnter)
				}
			},
			showSeat(player) {
				player.send(() => {
					if (!_status.sw_seatCSS) {
						_status.sw_seatCSS = true;
						const sw_seat = document.createElement('style')
						sw_seat.innerHTML = `
						.player > .swSeat {
							display: block;
							position: absolute;
							bottom: -16px;
							width: 100%;
							line-height: 16px;
							color: rgb(12 12 12);
							font-family: "shousha";
							font-size: 22px;
							font-weight: bold;
							text-align: center;
							text-shadow: 0 0 5px #e3ea9d, 0 0 2px #e3ea9d;
							white-space: nowrap;
							z-index: 100;
							pointer-events: none;
						}
						
						.player[data-position="0"] > .swSeat {
							bottom: -10px;
						}
						
					`
						document.head.appendChild(sw_seat)
					}
					if(!lib?.hooks?.checkUpdate?.has("showSeat")) {
						lib.hooks.checkUpdate.push(function(player) {
							const seat = player.getSeatNum();
							if (seat === 0) return;
							player.seat = seat;
							if (!player.node.swSeat) {
								player.node.swSeat = ui.create.div('.swSeat', player);
							}
							player.node.swSeat.innerHTML = get.cnNumber(seat, true);
						});
					}

				})
			},
			skillAnimation(player) {
				//客机异步加载图片
				syncFiles.push(...Array.from({ length: 8 }, (_, i) => `${lib.assetURL}extension/${swTool.extensionName}/assets/animation/skill/${i}.png`));
				player.send(() => {
					if (!_status.sw_skillAnimationCSS) {
						_status.sw_skillAnimationCSS = true;
						const sw_skillAnimation = document.createElement('style')
						sw_skillAnimation.innerHTML = swTool.skillAnimationCSS
						document.head.appendChild(sw_skillAnimation)
					}
					// if (!_status.skillAnimationIMG) {
					// 	_status.skillAnimationIMG = [];
					// 	(async () => {
					// 		const frameUrls = [
					// 			'https://img.picui.cn/free/2025/06/11/6849530fb9c74.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849531001478.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530cec7cf.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530cd68ca.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530d0f45e.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530d24997.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530ce31b7.png',
					// 			'https://img.picui.cn/free/2025/06/11/6849530f979ca.png'
					// 		];
					//
					// 		const list = Array.from({ length: 8 }, (_, i) => ({
					// 			src: `${lib.assetURL}extension/${swTool.extensionName}/assets/animation/skill/${i}.png`
					// 		}));
					//
					// 		function loadImageWithTimeout(src, timeout = 10000) {
					// 			return new Promise((resolve, reject) => {
					// 				const img = new Image();
					// 				let timer = setTimeout(() => {
					// 					img.onload = null;
					// 					img.onerror = null;
					// 					reject(new Error("Timeout"));
					// 				}, timeout);
					// 				img.src = src;
					// 				img.onload = () => {
					// 					clearTimeout(timer);
					// 					resolve(img);
					// 				};
					// 				img.onerror = () => {
					// 					clearTimeout(timer);
					// 					reject(new Error("Image failed"));
					// 				};
					// 			});
					// 		}
					//
					// 		await Promise.all(frameUrls.map(async (url, index) => {
					// 			const localSrc = list[index].src;
					// 			try {
					// 				const fileExists = await new Promise((resolve) => {
					// 					game.checkFile(localSrc, (num) => resolve(num === 1), () => resolve(false));
					// 				});
					//
					// 				if (fileExists) {
					// 					_status.skillAnimationIMG[index] = list[index];
					// 				} else {
					// 					const img = await loadImageWithTimeout(url);
					// 					_status.skillAnimationIMG[index] = img;
					// 				}
					// 			} catch (e) {
					// 				console.warn(`加载第${index + 1}帧失败：`, e.message);
					// 				_status.skillAnimationIMG[index] = { src: frameUrls[index] };
					// 			}
					// 		}));
					// 	})()
					// }

					//game.ensureDirectory()
				})
			},
			DIY() {
				syncFiles.push(..._status.syncFiles.filter(path => !path.includes("animation/skill/")));
			},
		}
		//判断是否开启，如果开启了对应的配置，就执行
		for (let funcName in playerFunction) {
			if (typeof playerFunction[funcName] === 'function' && lib.config[swTool.myURL() + 'game_' + funcName]) {
				playerFunction[funcName](player, version, config, banned_info)
			}
			if (typeof playerFunction[funcName] === 'function' && lib.config[swTool.myURL() + 'pack_' + funcName]) {
				playerFunction[funcName](player, version, config, banned_info)
			}
		}
		if(syncFiles.length) {
			player.send(async (files) => {
				const extensionURL = "https://raw.gitcode.com/LonelyWatch/noname_server_extension/raw/main/";
				game.checkFileList(files, function () {
					for (var i = 0; i < files.length; i++) {
						files[i] = extensionURL + swTool.extensionName + "/" + files[i].slice("extension/".length + swTool.extensionName.length + 1);
					}
					var n1 = 0,
						n2 = files.length;
					if(n2) {
						ui.updateResourceTip = ui.create.div('.resourceTip', "资源同步中...0%", ui.window, {
							color: "yellow",
							zIndex: "10000",
							bottom: "0%",
							opacity: "1",
						})
					}
					const onprogress = function (byte, total) {
						if (byte == -1) {
							byte = total;
						}
						if(ui.updateResourceTip) {
							if(byte < total) {
								ui.updateResourceTip.textContent = "资源同步中..." + Math.round((100 * byte) / total) + "%";
							}
							else {
								ui.updateResourceTip.textContent = "资源同步完成！";
								setTimeout(() => {
									ui?.updateResourceTip?.remove();
								}, 5000);
							}
						}
					};
					game.multiDownload(
						files,
						function () {
							n1++;
							onprogress(n1, n2);
						},
						function (e) {
							console.log("下载失败：" + e.source);
						},
						function () {
							onprogress(-1);
							// 同步完毕
							console.log("资源更新完毕");
						},
						function (current) {// 放到哪里
							return "extension/" + current.slice(extensionURL.length);
						},
					);
				});
				// TODO: 设置超时清除
			}, syncFiles)
		}
	},
	//重连和观战的同步角色逻辑
	modeCharacter({ player, version, config, banned_info }) {
		player.send(skills => {
			for (const s in skills) lib.skill[s] = skills[s]
		}, swTool.globalSkills)//发一下全局的全局技能
		//TODO: 此处可以优化
		if (_status.playerCharactersUse) {
			player.send(_status.playerCharactersUse, _status.playerCharacters, _status.style.innerHTML, swTool.copyCharacter)
		}
		if (_status.extensionCharacter) {
			swTool.send.call(player, 'DIY', packs => {
				// const selfLoad = lib.config[`extension_${swTool.extensionName}_enable`] &&
				// 	parseFloat(lib.config[`extension_${swTool.extensionName}_version`]) >= parseFloat(swTool.extensionVersion)
				for (const p in packs) {
					const pack = packs[p]
					for (const key in pack) {
						if (key in lib) {
							lib[key] = Object.assign(lib[key], pack[key])
						}
					}
					if (typeof pack.func === "function") {
						pack.func()
					}
					// if (selfLoad && pack.character) {
					// 	for (const key in pack.character) {
					// 		const img = lib.character[key]?.img;
					// 		if (typeof img === "string" && img.startsWith("http")) {
					// 			lib.character[key].img = `extension/${swTool.extensionName}/assets/character/${key}.jpg`;
					// 		}
					// 		const skin = lib.characterSubstitute[key] || []
					// 		for (const item of skin) {
					// 			if (Array.isArray(item) && item.length > 1) {
					// 				for (let i = 0; i < item[1].length; i++) {
					// 					if (typeof item[1][i] === 'string' && item[1][i].startsWith('img:http')) {
					// 						item[1][i] = `img:extension/${swTool.extensionName}/assets/character/${item[0]}.jpg`;
					// 					}
					// 				}
					// 			}
					// 		}
					// 	}
					// }
				}


			}, _status.extensionCharacter)
		}
	},
	addGroup(id, short, name, config) {
		game.addGroup(id, short, name, config)
		lib.translate["group_" + id] = short + "势力"
	},
	addNature(nature, translation, config, color, info) {
		game.addNature(nature, translation, config)
		lib.inpile_nature.add("nature");
		if (info) lib.translate[`sha_nature_${nature}_info`] = info
		const style = document.createElement('style')
		style.innerHTML = `
			.player .identity[data-color="${nature}"],
			div[data-nature="${nature}"],
			span[data-nature="${nature}"] {
			text-shadow: black 0 0 1px,${color} 0 0 2px,${color} 0 0 5px,${color} 0 0 10px,
			${color} 0 0 10px
			}
			`
		document.head.appendChild(style)
	},
	//模式初始化
	modeInit(player) {
		if (!_status.extensionMode) return;
		player.send(mode => {
			_status.extensionMode = mode
			const pack = ['character', 'dynamicTranslate', 'characterTitle']//skill card translate都会在loadmode中传递完毕
			for (var i of pack) {
				if (!mode[i]) continue
				for (var j in mode[i]) {
					if (lib[i]) lib[i][j] = mode[i][j]
				}
			}
			swTool.comboObject(game, mode.game);
			swTool.comboObject(ui, mode.ui);
			if (typeof mode.oninit === "function") mode.oninit();
		}, _status.extensionMode)
	},
	isObserving(playerid) {
		let observe = true
		if (playerid && lib.playerOL?.[playerid]) observe = false
		if (_status.waitingForPlayer) observe = false
		return observe
	},
	toString() {
		const args = Array.from(arguments);
		if (typeof args[0] == "function") {
			args.unshift("exec");
		}
		for (let i = 1; i < args.length; i++) {
			args[i] = get.stringifiedResult(args[i]);
		}
		return JSON.stringify(args)
	},
	send(name, ...args) {
		if (!swTool.strings[name]) {
			swTool.strings[name] = swTool.toString(...args)
		}
		return this.ws.send(swTool.strings[name])
	},
	getPlayer(id) {
		let player;
		if (lib.playerOL[id]) {
			player = lib.playerOL[id];
		} else if (game.connectPlayers) {
			player = game.connectPlayers.find(p => p.playerid === id)
		}
		return player
	},
	connectPlayers() {
		const players = game.connectPlayers || game.players
		const playertrue = []//真人
		const playersfalse = []//人坤
		players.forEach(p => {
			if (p === game.me || p.isOnline2()) playertrue.push(p)
			else playersfalse.push(p)
		})
		return [playertrue, playersfalse]
	},
	getSelecList() {
		const list = []
		const list2 = []
		const list3 = []
		const list4 = []
		const libCharacter = {};
		lib.configOL.characterPack.forEach(p => {
			const pack = lib.characterPack[p]
			for (const j in pack) if (lib.character[j]) libCharacter[j] = pack[j];
		})
		for (const i in lib.characterReplace) {
			const ix = lib.characterReplace[i]
			for (let j = 0; j < ix.length; j++) {
				if (!libCharacter[ix[j]] || lib.filter.characterDisabled(ix[j])) ix.splice(j--, 1);
			}
			if (ix.length) {
				list.push(i)
				list2.push(i)
				list4.addArray(ix)
				let bool = false
				for (const j of ix) {
					if (libCharacter[j][4] && libCharacter[j][4].includes('zhu')) {
						bool = true;
						break;
					}
				}
				(bool ? list2 : list3).push(i);
			}
		}
		for (const i in libCharacter) {
			if (list4.includes(i)) continue;
			if (lib.filter.characterDisabled(i, libCharacter)) continue;
			list.push(i)
			list2.push(i)
			list4.push(i)
			if (libCharacter[i][4] && libCharacter[i][4].contains('zhu')) list2.push(i)
			else list3.push(i);
		}
		return { list, list2, list3, list4, libCharacter }
	},
	getZhuList(list2) {
		const limit_zhu = lib.configOL.limit_zhu;
		if (!limit_zhu || limit_zhu == 'off') return list2.slice(0).sort(lib.sort.character);
		if (limit_zhu != 'group') {
			const num = (parseInt(limit_zhu) || 6);
			return list2.randomGets(num).sort(lib.sort.character);
		}
		const getGroup = function(name) {
			if (lib.characterReplace[name]) return lib.character[lib.characterReplace[name][0]][1];
			return lib.character[name][1];
		}
		const list2x = list2.slice(0);
		list2x.randomSort();
		for (var i = 0; i < list2x.length; i++) {
			for (var j = i + 1; j < list2x.length; j++) {
				if (getGroup(list2x[i]) == getGroup(list2x[j])) {
					list2x.splice(j--, 1);
				}
			}
		}
		list2x.sort(lib.sort.character);
		return list2x;
	},
	getPhone: function() {
		//获取浏览器navigator对象的userAgent属性（浏览器用于HTTP请求的用户代理头的值）
		var info = navigator.userAgent;
		//通过正则表达式的test方法判断是否包含“Mobile”字符串
		var isPhone = /mobile|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|OperaMini/i.test(info);
		//如果包含“Mobile”（是手机设备）则返回true
		return isPhone;
	},
	//来源于铝宝大佬的技能提示框
	skillTips: function(tipname, id) {
		var dibeijing = ui.create.div(".Fool-dibeijing", document.body);
		dibeijing.style.zIndex = 16;
		var skilltip = ui.create.div(".Fool-skilltip", dibeijing);
		skilltip.innerHTML = tipname;
		var href = document.getElementById(id);
		if (href) {
			var left = href.getBoundingClientRect().left;
			if (left) {
				if (swTool.getPhone()) left += href.offsetParent.offsetLeft;
				left += document.body.offsetWidth * 0.15;
				skilltip.style.left = left + "px";
				skilltip.style.top = (href.getBoundingClientRect().top + 30) + "px";
			}
		}
		dibeijing.listen(function(e) {
			e.stopPropagation();
			this.remove();
		});
	},
	// =function(str1,str2){
	// 	var temp='',numx=1;
	// 	while(numx!=10000){
	// 		temp+=get.rand(1,9)/numx;
	// 		numx=numx*10;
	// 	}
	// 	return "<a id='"+temp+"' style='color:unset' href=\"javascript:get.Fool_skillTips('"+str2+"','"+temp+"');\">※"+str1+"</a>";
	// },
	createCharacter(name, translate, sex, group, hp, skills, extens) {
		const copy = swTool.copyCharacter(name)
		const character = new lib.element.Character()
	},
	//此函数需要广播
	copyCharacter({ character, hp, skills, name, translate }) {
		const {
			sex = "male",
			group = "qun",
			trashBin = [],
		} = lib.character[character] || {};
		lib.character[name] = new lib.element.Character([sex, group, hp, skills])
		lib.character[name].maxHp = lib.character[name].hp = hp
		if (trashBin.some(t => t.includes("ext:"))) {
			lib.character[name].trashBin = trashBin
		}
		lib.character[name].trashBin.push(`character:${character}`)
		lib.character[name].trashBin.push(`die_audio:${character}`)
		lib.character[name].trashBin.remove('hiddenSkill')
		lib.translate[name] = translate
		return lib.character[name]
	},
	setPlayersSeat(first = game.zhu) {
		let seat = 1
		while (!first.next.seatNum || !first.seatNum) {
			first.seatNum = seat
			seat++
			first = first.next
		}
	},
	observeEnterHTML: `
	
.observeContainer {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    height: 180px;
    align-items: flex-start;
    align-content: flex-start;
    overflow: auto;
}

.observeContainer .item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    height: 150px;
    /* padding: 0 8px; */
    /* margin-bottom: 6px; */
    /*background-color: #d5e5ff;
    box-shadow: 0 1px 2px 1px rgb(0 0 0 / 49%);*/
    transition: background-color 0.2s ease, transform 0.2s ease;
    position: relative;
    border-radius: 4px;
}

.observeContainer .item .avatar {
    width: 100px;
    /* height: 100px; */
    left: 5%;
    /* top: 10px; */
    overflow: hidden;
    width: 100px;
    height: 120px;
    /* left: 10px; */
    /* top: 10px; */
    overflow: hidden;
    background-size: cover;
    background-position: top;
    background-repeat: no-repeat;
    /*border-radius: 20%;*/
    display: flex;
    justify-content: center;
    align-items: center;
}

.observeContainer .item .avatar .avatarName{
    top: 0px;
    font-size: 16px;
    font-family: "shousha";
    text-shadow: 0 0 2px black;
    background: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.4) 40%, rgba(0, 0, 0, 0.4) 60%, transparent 100%);
    z-index: 20;
    pointer-events: none;
    left: 0;
    width: 100%;
    text-align: center;
    white-space: nowrap;
}

.observeContainer .item .avatar .avatarButton{
    padding: 2px 0 4px 0;
    /* border: none; */
    /*background-color: #1d8cffb0;*/
    color: #ffffff;
    /* border-radius: 6px; */
    cursor: pointer;
    transition: background-color 0.3s;
    top: 80%;
    width: 100%;
}
.observeContainer .item .avatar .avatarButton.forbidEnter{
    background-color: #FF0936B0;
}
.observeContainer .item .avatar .avatarButton.forbidEnter:hover {
    background-color: #a11919;
}
.observeContainer .item .avatar .avatarButton.canEnter{
    background-color: #1d8cffb0;
}
.observeContainer .item .avatar .avatarButton.canEnter:hover {
    background-color: #1642b0;
}
	
	`,
	backpackHTML: `
	

.swContainer.backpack {
    width: 100%;
    height: 100%;
    background-color: rgb(44 39 39 / 38%);
    z-index: 10;
}
.swContainer.backpack * {
    text-shadow: none;
    box-sizing: border-box;
}
.swContainer.backpack .mainBackpack {
    --bgColor: #2c3645;
    border: 3px solid rgb(179, 201, 213);
    width: 60%;
    height: 70%;
    background-color: var(--bgColor);
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}
.swContainer.backpack .mainBackpack .bagContent {
    width: 100%;
    height: 100%;
    bottom: 0%;
    display: flex;
    justify-content: space-between;
    background: #2c3445;
    border: 2px solid #415259;
    padding: 5px;
    box-sizing: border-box;
    /*overflow-x: scroll;*/
}
.swContainer.backpack .mainBackpack .bagContent * {
    position: relative;
}
.swContainer.backpack .mainBackpack .bagContent .bagPanel {
    background-color: #9091cc;
    height: 100%;
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 100px;
    /*margin: 0 5px;*/
    padding: 3px;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .subtitle {
    width: 100%;
    text-align: center;
    color: black;
    flex-grow: 0;
    height: 8%;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 1.3em;
    flex-shrink: 0;
    overflow: scroll;
}
.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container {
    display: flex;
    align-items: center;
    padding: 15px;
    background-color: #f6f6ff;
    border-radius: 5px;
    box-sizing: border-box;
    gap: 10px;
    height: 100%;
    margin: 5px;
    overflow: hidden; /* 防止子元素溢出 */
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .avatar {
    width: 40%;
    height: 60%;
    min-width: 90px;
    min-width: 120px;
    background: rgba(0, 0, 0, 0.4);
    box-shadow: 0px -1px 5px black, 0px 3px 15px black;
    border-radius: 8px;
    top: 0;
    left: 0;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .avatar .avatarImg{
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    background-size: cover;
    background-position: top;
    background-repeat: no-repeat;
    border-radius: 8px;
}
.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .avatar .avatarName{
    top: 0px;
    font-size: 16px;
    font-family: "shousha";
    text-shadow: 0 0 2px black;
    background: linear-gradient(to right, transparent 0%, rgba(0, 0, 0, 0.4) 40%, rgba(0, 0, 0, 0.4) 60%, transparent 100%);
    z-index: 89;
    pointer-events: none;
    left: 0;
    width: 100%;
    text-align: center;
    white-space: nowrap;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .sw_info {
    height: 80%;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    overflow-y: auto;
    border-radius: 4px;
    margin: 0 10px;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .sw_info * {
    color: #1b1e2e;
    font-size: 24px;
    line-height: 1.2;
    text-shadow: #4f52b5 0 0 3px;
    font-family: "STXinwei", "xinwei";
}


.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .skillContainer {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    /*grid-template-rows: repeat(3, 1fr);*/
    align-items: flex-start;
    align-content: flex-start;
    overflow: auto;
    gap: 10px;
    padding: 10px;
    height: 100%;
    width: 100%;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .skillItem {
    transition: 0.2s;
    width: 100%;
    height: 30px;
    background-color: #d5e5ff;
    color: #000000;
    display: flex;
    justify-content: center;
    align-items: center;
    /*border-radius: 8px;*/
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    font-family: 'shousha';
    font-weight: 1000;
    border: 2px solid #564fbc;
}
.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .skillItem:hover {
    /*box-shadow: 0 0 1px 2px gold;*/
    background-color: #80a9ec;
    /*transform: translateY(2px);*/
    box-shadow: 0 4px 8px rgba(100, 100, 255, 0.2);
    border: 2px solid #564fbc;
}
.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .skillTip {
    position: absolute;
    background: #f5eded;
    width: 102%;
    height: fit-content;
    z-index: 10;
    padding: 6px;
    color: black;
    border: 3px solid #143308;
    border-radius: 3px;
    left: 0;
    display: none;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .backpackContainer {
    padding: 2px;
    gap: 10px;
    overflow-y: auto;
    font-family: system-ui;
    height: 100%;
    width: 100%;
    align-items: flex-start;
    align-content: flex-start;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .backpackContainer .backpackItem {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    height: 40px;
    padding: 0 8px;
    margin-bottom: 6px;
    background-color: #d5e5ff;
    /*border-radius: 8px;*/
    box-shadow: 0 1px 2px 1px rgb(0 0 0 / 49%);
    transition: background-color 0.2s ease, transform 0.2s ease;
    position: relative;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .backpackContainer .backpackItem:hover {
    background-color: #80a9ec;
    /*transform: translateY(2px);*/
    box-shadow: 0 4px 8px rgba(100, 100, 255, 0.2);
    border: 2px solid #564fbc;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .backpackContainer .backpackItem .itemName {
    max-width: 70%;
    font-size: 20px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: left;
    font-weight: 500;
    transition: font-size 0.2s;
    color: black;
    font-family: 'shousha';
    font-weight: bold;
}

.swContainer.backpack .mainBackpack .bagContent .bagPanel .sw_Container .backpackContainer .backpackItem .itemCount {
    max-width: 30%;
    font-size: 16px;
    font-weight: bold;
    color: #555;
    text-align: right;
    flex-shrink: 0;
}

.player:not([data-position="0"]) .sw_playerState {
    left: 0;
    top: 28px;
    width: 100%;
    font-size: 12px;
    text-align: center;
    white-space: nowrap;
    color: #fff500;
    text-shadow: 1px 1.5px 1px rgb(0 0 0);
}
body.layout-false .player[data-position="0"] .sw_playerState {
    left: 0;
    top: 28px;
    width: 100%;
    font-size: 12px;
    text-align: center;
    white-space: nowrap;
    color: #fff500;
    text-shadow: 1px 1.5px 1px rgb(0 0 0);
}
body.layout-true .player[data-position="0"] .sw_playerState {
    left: 60px;
    top: 16px;
    /* width: 100%; */
    font-size: 12px;
    text-align: center;
    white-space: nowrap;
    color: #fff500;
    text-shadow: 1px 1.5px 1px rgb(0 0 0);
    transform: translateX(-50%);
}


.sw_skillInfoDesc {
    position: absolute;
    width: 30%;
    border: 1px solid white;
    left: 35%;
    top: 40%;
    text-align: left;
    display: none;
    background-color: rgba(0, 0, 0, 0.8);
    color: white;
    font-family: "shousha", serif;
    z-index: 12;
    transform-origin: center bottom;
    pointer-events: none;
    opacity: 1;
    transition: opacity 0.3s ease;
}
.sw_skillInfoDesc.fade-out {
    opacity: 0;
}
	`,
	playerInnerHTML:/*css*/`
                    .player .playerskillList {
						color: #ffc400;
						font-weight: 800;
						text-shadow: 2px 2px 1.5px black;
						font-family: "SimHei", "Microsoft YaHei", sans-serif;
                        z-index: 90000000;
                    }
                    .player:not([data-position="0"]) .playerskillList {
                        width: 70px;
                        height: auto;
                        top: 40%;
                        border-radius: 5px;
                        text-align: center;
                        line-height: 1.4;
                        left: 20%;
                        padding: 0 10px;
                        font-size: 15px;
                    }
                    .player[data-position="0"] .playerskillList {
                        top: -25px;
                        left: 12px;
                    }
                `,
	getInfo(skill, player) {
		if (!player) player = '目标'
		const translation = get.translation(skill)
		let translate = lib.translate[skill + "_info"]
		if (translate === translation) {
			const info = lib.skill[skill]
			if (!info) return
			const content = info.intro.content
			if (typeof content === 'string') {
				translate = content
			} else {

			}
		}
		return translate
	},
	getSkillMapOfCharacter(characters, func) {
		// const allSkills = Object.keys(lib.skill)
		// const skillList = []
		const skillList2 = []
		const numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
		characters.forEach(s => {
			if (lib.character[s]["isUnseen"]) return;
			const theSkills = lib.character[s][3]
			theSkills.forEach(t => {
				// const moreSkills = allSkills.filter(cs=>cs.includes(t)&&cs!==t)
				const moreSkills = []
				numbers.forEach(n => {
					const skill = t + n
					if (skill in lib.skill) {
						moreSkills.push(t + n)
					}
				})
				const info = lib.skill[t]
				if (!info) return
				if (info.equipSkill) return;
				const derivation = (Array.isArray(info.derivation) ? [...theSkills, ...info.derivation] : [...theSkills, info.derivation]).filter(Boolean).addArray(moreSkills)
				derivation.forEach(is => {
					const groupSkill = lib.skill[is]
					if (!groupSkill || !swTool.getInfo(is)) return;
					const {
						subSkill,
						global,
						viewAs,
						chooseButton,
						mod,
						charlotte,
						equipSkill,
						content,
						nopop,
						dutySkill,
						hiddenSkill,
						juexingji,
						zhuSkill
					} = groupSkill
					if (!skillList2.includes(is) && !charlotte && !equipSkill && (subSkill || global || content || viewAs || chooseButton || mod) && !nopop && !hiddenSkill  /*!dutySkill && !juexingji && !zhuSkill*/) {
						// skillList.push({ skill: is, name: s })
						skillList2.push(is)
						func(is, s)
					}
				})
			})
		})
		// return skillList
	},

	//以下是岁儿大佬写的玩家之间交换控制权的代码
	auto(bool) {//这个auto是目标自己一个人应该收到的消息，其他人一概不管
		const fn = bool ? 'add' : 'remove'
		const replacePlayer = function(e) {
			if (!_status.auto || !game.notMe) return;
			game.swapPlayer(this || e.target.parentElement)
		}
		game.players.forEach(p => p[fn + "EventListener"](lib.config.touchscreen ? "touchend" : "click", replacePlayer))
		ui.arena.classList[fn]("observe")
		game.notMe = bool
		_status.auto = bool
		game.observe = bool
	},
	playerToobserve(player) {
		const auto = swTool.auto
		//如果只传了一个玩家，那就是要把一个玩家放上旁观
		//但这里还要确认一下这个是不是普通玩家
		//不然传入,一些字符串或者普通对象就不太合适了
		//还有就是这个玩家不能是人机，这里用的是判断ws和主机
		if (player instanceof ClientElement) return '只传一个ws没有用'
		if (player.ws instanceof ClientElement || player === game.me) {
			const id = get.id()
			const ws = player.ws
			swTool.observingId[ws.id] = ws
			lib.node.observing.push(ws)
			delete player.ws
			//这三步是把玩家的ws干掉
			game.broadcastAll((player, id, replaceNickname) => {
				delete lib.playerOL[player.playerid]
				lib.playerOL[id] = player
				player.playerid = id
				if (replaceNickname) {
					player.nickname = ''
					player.node.nameol.innerHTML = ''
				}
			}, player, id, replaceNickname)
			if (player === game.me) {
				auto(true)
				return '已经将主机换至旁观'
			}

			ws.send(auto, true)
			//删掉了客机的ws就变成人机了

			if (!ui.removeObserve && lib.node.observing.length) {
				ui.removeObserve = ui.create.system(
					"移除旁观",
					function() {
						lib.configOL.observe = false;
						if (game.onlineroom) {
							game.send("server", "config", lib.configOL);
						}
						while (lib.node.observing.length) {
							lib.node.observing.shift().ws.close();
						}
						this.remove();
						delete ui.removeObserve;
					},
					true,
					true
				);
			}

			return '此客机已经变为旁观'
		}
		//否则啥也不干
		return '虽然传入了一个玩家，但这是个没有ws的人机？'
	},
	observeToPlayer(player, player2) {

		const source = player instanceof PlayerElement ? player2 : player //这就是要上位的角色，是一个ws
		const target = player2 instanceof PlayerElement ? player2 : player //这是目标，一个玩家，可能是主机

		const sourceWs = source
		const targetWs = target.ws

		swTool.swapPlayer(target)

		target.ws = sourceWs;

		game.broadcastAll((target, targetId, sourceId) => {
			delete lib.playerOL[targetId]
			target.playerid = sourceId
			lib.playerOL[sourceId] = target
		}, target, target.playerid, source.id)

		source.send(target => {
			game.swapPlayer(game.me, target)
			game.me.setIdentity(game.me.identity)
		}, target)

		source.send(auto, true)

		if (targetWs) {
			lib.node.observing.push(targetWs)
			lib.node.observe.remove(sourceWs)
		}
	},
	noeSwapPlayer(source, target, replaceNickname) {
		const { ws: sourceWs, playerid: sourceId } = source
		const { ws: targeteWs, playerid: targetId } = target

		if (target.tempWsInfo || source.tempWsInfo) {
			source.viewNow = target.startId

			if (source.isOnline2()) {
				source.send(game.swapPlayer, target, source)
			} else {
				game.swapPlayer(source, target)
			}

			target.ws = target.tempWsInfo.ws
			source.ws = source.tempWsInfo.ws

			game.broadcastAll((source, target, sourceId, targetId) => {
					delete lib.playerOL[source.playerid]
					delete lib.playerOL[target.playerid]

					lib.playerOL[targetId] = target
					lib.playerOL[sourceId] = source

					target.playerid = targetId
					source.playerid = sourceId
				},
				source, target,
				source.tempWsInfo.playerid,
				target.tempWsInfo.playerid)

			delete target.tempWsInfo
			delete source.tempWsInfo
			delete source._controlMe
			delete target._controlNow
			return '单向换位又换回来了'
		}

		const tempId = get.id()

		target.tempWsInfo = { ws: target.ws, playerid: target.playerid }
		source.tempWsInfo = { ws: source.ws, playerid: source.playerid }

		game.broadcastAll((source, target, sourceId, targetId, tempId) => {
			delete lib.playerOL[targetId]
			lib.playerOL[sourceId] = target
			lib.playerOL[tempId] = source
			source.playerid = tempId
			target.playerid = sourceId
		}, source, target, sourceId, targetId, tempId)

		if (source.isOnline2()) {
			source.send(game.swapPlayer, source, target)
			source.isOnline2 = source.isOnline = () => true
		} else {
			game.swapPlayer(source, target)
		}


		target._controlMe = source
		source._controlNow = target

		source.viewNow = target.startId

		target.ws = source.ws


		return '已经单向换位'
	},
	douleSwapPlayer(source, target, replaceNickname) {
		const { ws, playerid: sourceId, nickname: sourceNickname } = source
		const { ws: ws2, playerid: targetId, nickname: targetNickname } = target
		game.broadcastAll((target, source, sourceId, targetId, sourceNickname, targetNickname, replaceNickname) => {
			target.playerid = sourceId
			source.playerid = targetId

			lib.playerOL[sourceId] = target
			lib.playerOL[targetId] = source

			if (replaceNickname) {
				target.nickname = sourceNickname
				source.nickname = targetNickname
				target.setNickname()
				source.setNickname()
			}

		}, target, source, sourceId, targetId, sourceNickname, targetNickname, replaceNickname)


		if (target === game.me || source === game.me) {
			game.swapPlayer(source, target)
		}


		ws && ws.send(game.swapPlayer, source, target)
		ws2 && ws2.send(game.swapPlayer, source, target)

		target.ws = ws
		source.ws = ws2

		return '已经双向换位'
	},
	swapPlayer(playerAndTarget) {//ws就是WebSocket，就是一个存数据的地方
		const { player: source, player2: target, replaceNickname, unidirectional } = playerAndTarget
		const PlayerElement = lib.element.Player, ClientElement = lib.element.Client

		if (source && !source.startId) {
			source.startId = source.playerid + "-" + get.translation(source)
			source.viewNow = source.startId
		}
		if (target && !target.startId) {
			target.startId = target.playerid + "-" + get.translation(target)
			target.viewNow = target.startId
		}

		if (source && !target) return swTool.playerToobserve(source, target)//玩家变旁观

		if (source instanceof ClientElement && target instanceof ClientElement) return '两个非玩家就不用换了'

		if (source instanceof PlayerElement && target instanceof PlayerElement) {
			if (unidirectional) return swTool.noeSwapPlayer(source, target, replaceNickname)//单向换位（相当于控制）
			return swTool.douleSwapPlayer(source, target, replaceNickname)//双向换位，最简单的
		}
		return swTool.observeToPlayer(source, target)//旁观变玩家
	},
}
window.swTool = swTool
